#include "volume.local.h"

#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>

#include "tools.filesystem.h"

namespace jolfs
{

LocalVolume::LocalVolume( const std::string& name, const std::string& path )
  : Volume( name ),
    path_ ( path )
{
  root_.setParent( 0 );
  root_.setName( name );
  root_.setSize( 0 );
  root_.setMode( UMODE(1,0,0) | GMODE(1,0,0) | OMODE(1,0,0) ); //TODO: fix this
  root_.setType( Entry::DIRECTORY );
  root_.setStatus( 0 );           //TODO: fix this
  root_.setAccessTime( 0 );       //TODO: fix this
  root_.setModificationTime( 0 ); //TODO: fix this
  root_.setCreationTime( 0 );     //TODO: fix this
  load( path_ );
}

LocalVolume::LocalVolume( const LocalVolume& other )
  : Volume( other       ),
    path_ ( other.path_ )
{
  load( path_ );
}

LocalVolume::~LocalVolume()
{
}

bool LocalVolume::load( const std::string& path )
{
  loadDirectory( path, &root_ );
  return true;
}

void LocalVolume::loadDirectory( const std::string& path, Entry* parent )
{
  DIR* dir = opendir( path.c_str() );
  
  struct dirent* e;
  struct stat*   s = new struct stat;
  
  // sanity check
  //TODO: handle error
  if ( dir == 0 )
    return;
  
  while ( dir != 0 )
  {
    e = readdir( dir );
    // sanity check
    if ( e != 0 )
    {
      // Skip dummy links
      if ( strcmp(e->d_name, ".")==0 || strcmp(e->d_name,"..")==0 )
        continue;
      Entry* entry = new Entry();
      entry->setName( e->d_name );
      entry->setParent( parent );
      // TODO: handle more file types, but are DT_REG and DT_DIR posix or linux or mac?
      switch ( e->d_type )
      {
        case DT_REG:
          entry->setType( Entry::FILE );
          break;
        case DT_DIR:
          entry->setType( Entry::DIRECTORY );
          break;
        case DT_LNK:
          entry->setType( Entry::SYMBOLIC_LINK );
          break;
        case DT_SOCK:
          entry->setType( Entry::SOCKET );
          break;
        case DT_BLK:
          entry->setType( Entry::BLOCK_DEVICE );
          break;
        case DT_CHR:
          entry->setType( Entry::CHARACTER_DEVICE );
          break;
        case DT_FIFO:
          entry->setType( Entry::NAMED_PIPE );
          break;
        default:
          entry->setType( Entry::UNKNOWN );
          break;
      }
      stat( (path + "/" + entry->getName()).c_str(), s );
      entry->setSize( s->st_size );
      
      ::mode_t mode = s->st_mode;
      entry->setMode(
        UMODE( (S_IRUSR & mode)!=0, (S_IWUSR & mode)!=0, (S_IXUSR & mode)!=0 ) |
        GMODE( (S_IRGRP & mode)!=0, (S_IWGRP & mode)!=0, (S_IXGRP & mode)!=0 ) |
        OMODE( (S_IROTH & mode)!=0, (S_IWOTH & mode)!=0, (S_IXOTH & mode)!=0 )
      );
      
      entry->setAccessTime      ( s->st_atime );
      entry->setModificationTime( s->st_mtime );
      entry->setCreationTime    ( s->st_ctime );
      parent->addChild( entry );
      
      if ( entry->getType()==Entry::DIRECTORY )
        loadDirectory( path + "/" + entry->getName(), entry );
    }
    else
    {
      if ( errno!=0 )
      {
        closedir( dir );
        return;
        //TODO: handle error
      }
      else
        break;
    }
  }
  closedir( dir );
}

void LocalVolume::addEntry( Entry* entry )
{
    root_.addChild( entry );
}

EntryList LocalVolume::getEntries() const
{
  return root_.getChildren();
}

Entry* LocalVolume::getRoot()
{
  return &root_;
}

const Entry* LocalVolume::getRoot() const
{
  return &root_;
}

const Entry* LocalVolume::getEntry( const std::string& path ) const
{
  //TODO: should really store names in a hashmap to speedup path lookup :(
  std::vector<std::string> tokenized_path = explode( path );
  
  if ( tokenized_path.empty() )
    return 0;
  
  const Entry* entry = &root_;
  while ( entry != 0 )
  {
    entry = entry->getChild( tokenized_path.at(0) );
    tokenized_path.erase(tokenized_path.begin());
    
    // Wrong file, not present
    if ( entry != 0 && tokenized_path.empty() )
      return entry;
  }
  
  return 0;
}

Entry* LocalVolume::getEntry( const std::string& path )
{
  //TODO: should really store names in a hashmap to speedup path lookup :(
  std::vector<std::string> tokenized_path = explode( path );
  
  if ( tokenized_path.empty() )
    return 0;
  
  Entry* entry = &root_;
  while ( entry != 0 )
  {
    entry = entry->getChild( tokenized_path.at(0) );
    tokenized_path.erase(tokenized_path.begin());
    
    // Wrong file, not present
    if ( entry != 0 && tokenized_path.empty() )
      return entry;
  }
  
  return 0;
}

} // namespace jolfs